//--------------------------------------------- // 
#include "stdafx.h"                             // <--- this include is necessary in order to use precompiled headers in visual studio. 
//--------------------------------------------- //      it must be the first commmand in each cpp file
#include "AdvancedGraphics.h"                  // <--- include your header file
//--------------------------------------------- // 
#include "Properties.h"                         // <--- this include file will import all properties available (used below)
#include "GeoXOutput.h"                         // <--- this include lets you output text in the main window
#include "FileDialogs.h"						// <--- this include lets you create file dialogs to open or save files
#include <fstream>								// <--- this include lets you read files
#include <sstream>								// <--- this include lets you parse strings
#include <LinearAlgebra.h>						// <--- this include lets you use vectors and their operations
//--------------------------------------------- //
#include <Qt\qcolordialog.h>
                                                //
                                                //
IMPLEMENT_GEOX_CLASS( AdvancedGFX ,0 )    // <--- names the class, sets version number to zero.
{                                               //      increment later if you add properties and still want to read old example files
   BEGIN_CLASS_INIT( AdvancedGFX );       // <--- starts the properties declartion

   ADD_NOARGS_METHOD(AdvancedGFX::LoadModel)
   ADD_NOARGS_METHOD(AdvancedGFX::UpdateParams)
   ADD_NOARGS_METHOD(AdvancedGFX::PickColor)
   ADD_NOARGS_METHOD(AdvancedGFX::ToggleWireframe)
   ADD_NOARGS_METHOD(AdvancedGFX::RayTrace)
   ADD_NOARGS_METHOD(AdvancedGFX::UpdateImage)
   ADD_FLOAT32_PROP( pathtrace_correction_factor, 0)
   ADD_FLOAT32_PROP ( specular_intensity, 0)
   ADD_BOOLEAN_PROP(enable_shadows, 0)
   ADD_BOOLEAN_PROP(enable_reflection, 0)
   ADD_BOOLEAN_PROP(enable_box_filter_AA, 0)
   ADD_BOOLEAN_PROP(divide_subpixels, 0)
   ADD_BOOLEAN_PROP(jitter_around_subpixels, 0)
   ADD_INT32_PROP(rays_per_pixel, 0)
   ADD_BOOLEAN_PROP(enable_gaussian_weighting, 0)
   ADD_FLOAT32_PROP(gweight_sigma, 0)
   ADD_BOOLEAN_PROP(enable_bvh, 0)
   ADD_BOOLEAN_PROP(enable_per_vertex_normals, 0)
  
   ADD_BOOLEAN_PROP(enable_pathtracer, 0)
   ADD_BOOLEAN_PROP(enable_refraction, 0)
}  


AdvancedGFX::AdvancedGFX() {        // <--- constructor - set reasonable default parameters
	specular_intensity = 12;
	enable_shadows = true;
	enable_reflection = false;
	enable_box_filter_AA = false;
	rays_per_pixel = 10;
	enable_gaussian_weighting = false;
	gweight_sigma = 0.5;
	jitter_around_subpixels = false;
	divide_subpixels = false;
	enable_per_vertex_normals = true;
	enable_pathtracer = false;
	enable_refraction = true;
	
	pathtrace_correction_factor = 1.0;

	enable_bvh = true;

} 

void AdvancedGFX::UpdateImage() {
	widget_3d->createAndShowIMG(pathtrace_correction_factor);
}

void AdvancedGFX::LoadCornell() {

}

QWidget* AdvancedGFX::createViewer() {
   widget_3d = new GFXWidget3D();


   LoadModel("../Models/box.obj");
   LoadModel("../Models/wall1.obj");
   LoadModel("../Models/wall2.obj");
   
   //LoadCornell();
   return widget_3d;
}

void AdvancedGFX::UpdateParams() {
	widget_3d->UpdateParams(specular_intensity, enable_shadows, enable_reflection, enable_per_vertex_normals);
}

void AdvancedGFX::ToggleWireframe() {
	widget_3d->toggleWireframe();
}

void AdvancedGFX::PickColor() {
	QColor color = QColorDialog::getColor(Qt::white, widget_3d);
    if (color.isValid())
    {	
		widget_3d->setColor(makeVector3f(color.redF(), color.greenF(), color.blueF()));
	}

}

void AdvancedGFX::RayTrace() {
	//widget_3d->UpdateParams(specular_intensity, enable_shadows, enable_reflection, enable_box_filter_AA);
	UpdateParams();
	AAMode aa;
	aa.gaussianWeightingSigma = gweight_sigma;
	if (gweight_sigma == 0)
		aa.gaussianWeightingSigma = 1;
	aa.nRaysPerPixel = rays_per_pixel;
	aa.enabled = enable_box_filter_AA;
	aa.useGaussianWeighting = enable_gaussian_weighting;
	aa.useDivideInSubPixel = divide_subpixels;
	aa.useSubPixelJitter = jitter_around_subpixels;

	widget_3d->raytraceRender( enable_pathtracer, enable_refraction, enable_bvh, aa );
}

void AdvancedGFX::LoadModel() {
		string filename = 
		FileDialogs::getOpenFileName(widget_3d, "Open .obj model file", "Wavefront Object File (*.obj);;All Files (*.*)");
		LoadModel(filename);
}

void AdvancedGFX::LoadModel(string filename) {
	Matrix4f transform;
	transform.setIdentity();
	LoadModel(filename, transform);
}

typedef float Vertex[3];
typedef float Normal[3];


void AdvancedGFX::LoadModel(string filename, Matrix4f transform) {
	if (!filename.empty()) {
		output << "Parsing file: \"" << filename << "\"\n";
		
		std::ifstream file(filename);
		std::string line;
		
		int i = 0; // Line counter
		float x, y, z; // Temp storage for vector coordinates 
		
		struct ObjectVertex {
			ObjectVertex() : normal_index(-1), index(-1), texture_index(-1), next_index(-1) {}

			int index;
			Vertex pos;
			int normal_index;
			int texture_index;
			int next_index;
		};

		struct ObjectNormal {
			Normal n;
		};

		std::vector<ObjectNormal > normals;

		std::vector< VertexPositionNormal> final_buffer;
		std::vector<ObjectVertex > vertices;
		std::vector<ObjectVertex > additional_vertices;
		std::vector<unsigned int > indices;

		string token;

		float min_y = 0.5f;

		while(getline(file, line))
		{
			++i;

			std::stringstream linestream(line);
			std::string token;


			if (!(linestream >> token)) continue; // Go to next line when this one is empty

			if (token == "v") {
				ObjectVertex vert;
				linestream >> vert.pos[0] >> vert.pos[1] >> vert.pos[2];
				//vert.normal[0] = 0.f; vert.normal[1] = 0.f; vert.normal[2] = 0.f;

				vertices.push_back(vert);
			}
			else if (token == "vn") { //TODO: Compute normals later

				ObjectNormal ns;
				linestream >> ns.n[0] >> ns.n[1] >> ns.n[2];
				normals.push_back(ns);
			}
			else if (token == "f") { // TODO: Stop ignoring texture coords and normals mapped to faces
				//Eat x vertex declarations until new line
				std::string vertexdecl;
				unsigned int vtn_indices[3];
				std::vector<unsigned int> faceIndices;

				

				while ((linestream >> vertexdecl)) {

					uint ii = 0;
					for (int k = 0; k < 3; k++) {
						int occ = vertexdecl.find('/');

						if (occ == -1) {
							std::stringstream(vertexdecl) >> vtn_indices[ii++];
							break;
						}

						std::stringstream(vertexdecl.substr(0, occ)) >> vtn_indices[ii++];
						vertexdecl = vertexdecl.substr(occ + 1); // Read only vertex #
					}

					
					ObjectVertex* v = &vertices.at(vtn_indices[0]-1);
					if (v->normal_index == -1) {
						v->index = final_buffer.size();

						v->normal_index = vtn_indices[2];

						VertexPositionNormal vn;
						for (int i = 0; i < 3; i++) {
							vn.position[i] = v->pos[i];
							vn.normal[i] = normals.at(v->normal_index-1).n[i];
						}
						final_buffer.push_back(vn);
					}
					else {
						while (true) {
							if (v->normal_index == vtn_indices[2]) break;

							if (v->next_index == -1) {

								v->next_index = additional_vertices.size();
								ObjectVertex nv;
								for (int i = 0; i < 3; i++) nv.pos[i] = v->pos[i];
								v = &nv;
								v->index = final_buffer.size();
								v->normal_index = vtn_indices[2];
								additional_vertices.push_back(nv);

								VertexPositionNormal vn;
								for (int i = 0; i < 3; i++) {
									vn.position[i] = v->pos[i];
									vn.normal[i] = normals.at(v->normal_index - 1).n[i];
								}

								
								final_buffer.push_back(vn);
								break;
							}
							else v = &additional_vertices.at(v->next_index);

						}
					}

					faceIndices.push_back(v->index);
				}

				//Tesselate (if neccessary) and add to model's indices
				for (unsigned int j = 2; j < faceIndices.size(); j++)
				{
					indices.push_back(faceIndices.at(0));
					indices.push_back(faceIndices.at(j - 1) );
					indices.push_back(faceIndices.at(j));
				}
			}

			/*std::istringstream linestream(line);
			if (!(linestream >> token)) continue; // Go to next line when this one is empty
			
			if (token == "v") {
				linestream >> x >> y >> z;
				VertexPositionNormal vert;
				
				Vector4f transformed_vertex = transform * makeVector4f(x, y, z, 1.f);
				vert.position = projectHomogeneous4To3(transformed_vertex);
				
				vert.normal.setZero();
				vertices.push_back(vert);
				if (y < min_y) min_y = y;
				//output << token << ": (" << x << ", " << y << ", " << z << ")\n";
			} else if (token == "vn") { //these normals are ignored later
				linestream >> x >> y >> z;
				normals.push_back(makeVector3f(x, y, z));
			} else if (token == "f") { // TODO: Stop ignoring texture coords and normals mapped to faces
				//Eat x vertex declarations until new line
				string vertexdecl;
				unsigned int index;
				std::vector<unsigned int> faceIndices;

				while((linestream >> vertexdecl)) {
					int occ = vertexdecl.find('/');
					vertexdecl = vertexdecl.substr(0, occ); // Read only vertex #
					istringstream( vertexdecl ) >> index;
					faceIndices.push_back(index);
				}
				
				//Tesselate (if neccessary) and add to model's indices
				for (unsigned int j = 2; j < faceIndices.size(); j++)
				{
					indices.push_back(faceIndices.at(0) - 1);
					indices.push_back(faceIndices.at(j-1) - 1);
					indices.push_back(faceIndices.at(j) - 1);
				}
			}*/
		}

		output << "Parsed " << i << " lines with " << vertices.size() << " vertices, " << (indices.size() / 3) << " triangles and " << normals.size() << " normals... \n";
		output << "Computing Normals...";

		//Compute Normals
		/*for (unsigned int f = 0; f < indices.size() / 3; f++) {
			Vector3f v1 = vertices.at(indices.at(f*3)).position;
			Vector3f v2 = vertices.at(indices.at(f*3+1)).position;
			Vector3f v3 = vertices.at(indices.at(f*3+2)).position;
			
			Vector3f normal = GFXWidget3D::Calculate_triangle_normal(v1, v2, v3);

			vertices.at(indices.at(f*3)).normal += normal;
			vertices.at(indices.at(f*3+1)).normal += normal;
			vertices.at(indices.at(f*3+2)).normal += normal;
		}
		
		output << "Done!\n";

		float yadd = min_y < 0.5f ? 0.5f - min_y : 0.f;

		for (unsigned int v = 0; v < vertices.size(); v++) {
			vertices.at(v).normal.normalize();
			//vertices.at(v).position[1] += yadd;
		}*/
			
		
		Model* newModel = new Model();
		newModel->indices = indices;
		newModel->vertices = final_buffer;
		//newModel.vertex_normals = normals;
		
		widget_3d->addModel(newModel);
	}

	return;
}

